import { isProdMode } from "@/utils/env";
import { error } from "@/utils/log";
import { isEqual } from "lodash-es";
import {
  computed,
  getCurrentInstance,
  nextTick,
  onUnmounted,
  reactive,
  ref,
  toRaw,
  unref,
  watchEffect,
} from "vue";
import {
  ModalMethods,
  ModalProps,
  ReturnMethods,
  UseModalInnerReturnType,
  UseModalReturnType,
} from "../typing";
import { Fn } from "/#/index";
import { Nullable } from "/#/global";
import { tryOnUnmounted } from "@vueuse/core";
import { isFunction } from "@/utils/is";

const dataTransfer = reactive<any>({});
const visibleData = reactive<{ [key: number]: boolean }>({});

export function useModal(): UseModalReturnType {
  const modalMethods = ref<Nullable<ModalMethods>>(null);
  const loaded = ref<Nullable<boolean>>(false);
  const uid = ref<string>("");

  function register(modalMethod: ModalMethods, uuid: string) {
    if (!getCurrentInstance()) {
      throw new Error(
        "useModal() can only be used inside setup() or functional components!"
      );
    }
    uid.value = uuid;
    isProdMode() &&
      onUnmounted(() => {
        modalMethods.value = null;
        loaded.value = false;
        dataTransfer[unref(uid)] = null;
      });
    if (unref(loaded) && isProdMode() && modalMethod === unref(modalMethods))
      return;

    modalMethods.value = modalMethod;
    loaded.value = true;
    modalMethod.emitVisible = (visible: boolean, uid: number) => {
      visibleData[uid] = visible;
    };
  }

  const getInstance = () => {
    const instance = unref(modalMethods);
    if (!instance) {
      error("useModal instance is undefined!");
    }
    return instance;
  };
  const methods: ReturnMethods = {
    closeModal: () => {
      getInstance()?.setModalProps({ modelValue: false });
    },
    openModal: <T = any>(modelValue = true, data?: T, openOnSet = true): void => {
      getInstance()?.setModalProps({
        modelValue: modelValue,
      });

      if (!data) return;
      const id = unref(uid);
      if (openOnSet) {
        dataTransfer[id] = null;
        dataTransfer[id] = toRaw(data);
        return;
      }
      const equal = isEqual(toRaw(dataTransfer[id]), toRaw(data));
      if (!equal) {
        dataTransfer[id] = toRaw(data);
      }
    },
    getVisible: computed((): boolean => {
      return visibleData[~~unref(uid)];
    }),
    setModalProps: (props: Partial<ModalProps>): void => {
      getInstance()?.setModalProps(props);
    },
    changeLoading: (loading = true) => {
      getInstance()?.setModalProps({ loading });
    },
    changeOkLoading: (loading = true) => {
      getInstance()?.setModalProps({ confirmLoading: loading });
    },
  };
  return [register, methods];
}

export function useModalInner(callbackFn?: Fn): UseModalInnerReturnType {
  const modalInstanceRef = ref<Nullable<ModalMethods>>(null);
  const currentInstance = getCurrentInstance();
  const uidRef = ref<string>("");

  const getInstance = () => {
    const instance = unref(modalInstanceRef);
    if (!instance) {
      error("useModalInner instance is undefined!");
    }
    return instance;
  };

  const register = (modalInstance: ModalMethods, uuid: string) => {
    isProdMode() &&
      tryOnUnmounted(() => {
        modalInstanceRef.value = null;
      });

    uidRef.value = uuid;
    modalInstanceRef.value = modalInstance;
    currentInstance?.emit("register", modalInstance, uuid);
  };

  watchEffect(() => {
    const data = dataTransfer[unref(uidRef)];
    if (!data) return;
    if (!callbackFn || !isFunction(callbackFn)) return;
    nextTick(() => {
      callbackFn(data);
    });
  });

  const InnerMethods = {
    closeModal: () => {
      getInstance()?.setModalProps({ modelValue: false });
    },
    setModalProps: (props: Partial<ModalProps>) => {
      getInstance()?.setModalProps(props);
    },
  };

  return [register, InnerMethods];
}
